﻿Imports System.Text
Imports System
Imports System.IO
Imports System.IO.Compression
Imports System.Runtime.Serialization.Formatters.Binary

''' <summary>
''' Represents SettingsManager class
''' </summary>
Public Class SettingsManager
	Private baseSettings As SettingsNode

	''' <summary>
	''' Creates new object of SettingsManager
	''' </summary>
	Public Sub New()
		baseSettings = New SettingsNode()
	End Sub


	Default Public ReadOnly Property Item(path As String) As Hashtable
		Get
			Return baseSettings.GetNode(path).Nodes
		End Get
	End Property

	''' <summary>
	''' Adds a Path to current collection of Settings
	''' </summary>
	''' <param name="path">Path as string to node</param>
	''' <remarks></remarks>
	Public Sub AddNode(path As String)
		baseSettings.AddNode(path)
	End Sub

	''' <summary>
	''' Adds a Path to current collection of Settings
	''' </summary>
	''' <param name="nodes">Pathelements to be added</param>
	Public Sub AddNode(ParamArray nodes As String())
		AddNode(CombineNode(nodes))
	End Sub

	''' <summary>
	''' Adds a value to the settings
	''' </summary>
	''' <typeparam name="T">Type of value</typeparam>
	''' <param name="path">Path as string</param>
	''' <param name="value">value</param>
	Public Sub AddNode(Of T)(path As String, value As T)
		baseSettings.AddNode(path, value)
	End Sub

	''' <summary>
	''' Adds a value to the settings
	''' </summary>
	''' <typeparam name="T">Type of value</typeparam>
	''' <param name="value">value</param>
	''' <param name="nodes">Pathelements</param>
	Public Sub AddNode(Of T)(value As T, ParamArray nodes As String())
		AddNode(CombineNode(nodes), value)
	End Sub

	''' <summary>
	''' Removes a node completely.
	''' </summary>
	''' <param name="path">Path of node</param>
	Public Sub RemoveNode(path As String)
		baseSettings.RemoveNode(path)
	End Sub

	''' <summary>
	''' Removes a node completely.
	''' </summary>
	''' <param name="nodes">Pathelements</param>
	Public Sub RemoveNode(ParamArray nodes As String())
		RemoveNode(CombineNode(nodes))
	End Sub

	''' <summary>
	''' Will return paths SettingsNode
	''' </summary>
	''' <param name="path">Path to node</param>
	''' <returns>returns a settingsnode</returns>
	Public Function GetNode(path As String) As SettingsNode
		Return baseSettings.GetNode(path)
	End Function

	''' <summary>
	''' Will return paths SettingsNode
	''' </summary>
	''' <param name="nodes">Pathelements</param>
	''' <returns>returns a settingsnode</returns>
	Public Function GetNode(ParamArray nodes As String()) As SettingsNode
		Return GetNode(CombineNode(nodes))
	End Function

	''' <summary>
	''' Returns value
	''' </summary>
	''' <typeparam name="T">type of returned value</typeparam>
	''' <param name="path">Path to value</param>
	''' <returns>Value of path</returns>
	Public Function GetNode(Of T)(path As String) As T
		Return baseSettings.GetNode(Of T)(path)
	End Function

	''' <summary>
	''' Returns value
	''' </summary>
	''' <typeparam name="T">type of returned value</typeparam>
	''' <param name="nodes">Path to value</param>
	''' <returns>Value of path</returns>
	Public Function GetNode(Of T)(ParamArray nodes As String()) As T
		Return GetNode(Of T)(CombineNode(nodes))
	End Function

	''' <summary>
	''' Will save current settings compressed to path
	''' </summary>
	''' <param name="path">path to savefile</param>
	Public Sub Save(path As String)
		Using fS As New FileStream(path, FileMode.Create)
			Using gS As New GZipStream(fS, CompressionMode.Compress, False)
				Dim bF As New BinaryFormatter()
				bF.AssemblyFormat = Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
				bF.TypeFormat = Runtime.Serialization.Formatters.FormatterTypeStyle.XsdString
				bF.Serialize(gS, baseSettings)
			End Using
		End Using
	End Sub

	''' <summary>
	''' Will load settings into current settingsmanager!
	''' </summary>
	''' <param name="path">Path to savefile</param>
	''' <exception cref="System.IO.FileNotFoundException">Thrown if savefile is not found</exception>
	Public Sub Load(path As String)
		If Not File.Exists(path) Then
			Throw New FileNotFoundException()
		End If
		Using fS As New FileStream(path, FileMode.Open)
			Using gS As New GZipStream(fS, CompressionMode.Decompress, False)
				Dim bF As New BinaryFormatter()
				bF.AssemblyFormat = Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
				bF.TypeFormat = Runtime.Serialization.Formatters.FormatterTypeStyle.XsdString
				baseSettings = DirectCast(bF.Deserialize(gS), SettingsNode)
			End Using
		End Using
	End Sub

	''' <summary>
	''' Will create a useable string for alle Settings-Tasks.
	''' </summary>
	''' <param name="nodes">Pathelements</param>
	''' <returns>Useable String</returns>
	Public Shared Function CombineNode(ParamArray nodes As String()) As String
		Return String.Join("~", nodes)
	End Function
End Class
''' <summary>
''' <para>Represents a node in the settings which contains more node and/or values.</para>
''' <para>This is used for serialization in SettingsManager.</para>
''' </summary>
<Serializable()> _
Public Class SettingsNode
	Protected _nodes As Hashtable
	''' <summary>
	''' Returns current nodes collection.
	''' </summary>
	Public ReadOnly Property Nodes As Hashtable
		Get
			Return _nodes
		End Get
	End Property

	''' <summary>
	''' Generates a new object of SettingsNode.
	''' </summary>
	Public Sub New()
		_nodes = New Hashtable()
	End Sub

	''' <summary>
	''' This will add the provided node into the current collection of settings.
	''' </summary>
	''' <param name="path">Path to the Node.</param>
	''' <exception cref="System.ArgumentException">Thrown if "path" is Null or empty.</exception>
	Public Sub AddNode(path As String)
		If String.IsNullOrEmpty(path) Then
			Throw New ArgumentNullException()
		End If

		Dim items As New Queue(Of String)(path.Split("~"c))
		Dim item As String = items.Dequeue()

		If Not Nodes.ContainsKey(item) Then
			Nodes.Add(item, New SettingsNode())
		End If
		If Nodes(item).[GetType]() Is GetType(SettingsNode) AndAlso items.Count() > 0 Then
			TryCast(Nodes(item), SettingsNode).AddNode(String.Join("~", items.ToArray()))
		End If
	End Sub

	''' <summary>
	''' Will add the provided value into the provided path.
	''' </summary>
	''' <typeparam name="T">Type of the value</typeparam>
	''' <param name="path">Path of the value</param>
	''' <param name="value">Value the path should have</param>
	''' <exception cref="System.ArgumentException">Thrown if "path" is Null or empty</exception>
	''' <exception cref="System.InvalidOperationException">Thrown if node does not contain part of the path</exception>
	''' <exception cref="System.InvalidCastException">Thrown if the end node is not of type T</exception>
	Public Sub AddNode(Of T)(path As String, value As T)
		If String.IsNullOrEmpty(path) Then
			Throw New ArgumentNullException()
		End If

		Dim items As New Queue(Of String)(path.Split("~"c))
		Dim item As String = items.Dequeue()

		If items.Count() > 0 Then
			If Not Nodes.ContainsKey(item) Then
				Nodes.Add(item, New SettingsNode())
			End If
			If Nodes(item).[GetType]() Is GetType(SettingsNode) Then
				TryCast(Nodes(item), SettingsNode).AddNode(Of T)(String.Join("~", items.ToArray()), value)
			Else
				Throw New InvalidCastException()
			End If
		Else
			If Not Nodes.ContainsKey(item) Then
				Nodes.Add(item, value)
			Else
				Throw New InvalidOperationException()
			End If
		End If
	End Sub

	''' <summary>
	''' Will replace the value of an node with provided
	''' </summary>
	''' <typeparam name="T">Type of value, does not have to be specified.</typeparam>
	''' <param name="path">Path to the value.</param>
	''' <param name="value">Value the path should apply</param>
	''' <exception cref="System.ArgumentException">Thrown if path is empty.</exception>
	''' <exception cref="System.Collections.Generic.KeyNotFoundException">Thrown if current nodes do not contain part of path</exception>
	''' <exception cref="System.InvalidCastException">Thrown if part of path is not type "SettingsNode"</exception>
	Public Sub EditNode(Of T)(path As String, value As T)
		If String.IsNullOrEmpty(path) Then
			Throw New ArgumentNullException()
		End If

		Dim items As New Queue(Of String)(path.Split("~"c))
		Dim item As String = items.Dequeue()

		If items.Count() > 0 Then
			If Not Nodes.ContainsKey(item) Then
				Throw New KeyNotFoundException()
			End If
			If Nodes(item).[GetType]() Is GetType(SettingsNode) Then
				TryCast(Nodes(item), SettingsNode).EditNode(Of T)(String.Join("~", items.ToArray()), value)
			Else
				Throw New InvalidCastException()
			End If
		Else
			If Nodes.ContainsKey(item) Then
				Nodes(item) = value
			Else
				Throw New KeyNotFoundException()
			End If
		End If
	End Sub

	''' <summary>
	''' This will remove a node. Completely.
	''' </summary>
	''' <remarks><para>Use only if you're 100% sure. Will remove all things in the node.</para>
	''' <para>If there are more SettingNodes in this Node, they will be removed.</para></remarks>
	''' <param name="path">Path to the node</param>
	''' <exception cref="System.ArgumentException">Thrown if "path" is null or empty</exception>
	''' <exception cref="System.Collections.Generic.KeyNotFoundException">Thrown if the node is not found in node collection.</exception>
	Public Sub RemoveNode(path As String)
		If String.IsNullOrEmpty(path) Then
			Throw New ArgumentNullException()
		End If

		Dim items As New Queue(Of String)(path.Split("~"c))
		Dim item As String = items.Dequeue()

		If Nodes.ContainsKey(item) Then
			If items.Count() > 0 Then
				TryCast(Nodes(item), SettingsNode).RemoveNode(String.Join("~", items.ToArray()))
			Else
				Nodes.Remove(item)
			End If
		Else
			Throw New KeyNotFoundException()
		End If
	End Sub

	''' <summary>
	''' Returns the node for any usage. Don't know why someone should use this.
	''' </summary>
	''' <param name="path">Path to the Node.</param>
	''' <returns>SettingsNode out of current node.</returns>
	''' <exception cref="System.ArgumentException">Thrown if path is empty.</exception>
	''' <exception cref="System.Collections.Generic.KeyNotFoundException">Thrown if current nodes do not contain part of path</exception>
	''' <exception cref="System.InvalidCastException">Thrown if part of path is not type "SettingsNode"</exception>
	Public Function GetNode(path As String) As SettingsNode
		If String.IsNullOrEmpty(path) Then
			Throw New ArgumentNullException()
		End If

		Dim items As New Queue(Of String)(path.Split("~"c))
		Dim item As String = items.Dequeue()

		If Nodes.ContainsKey(item) Then
			If Nodes(item).[GetType]() Is GetType(SettingsNode) Then
				If items.Count() > 0 Then
					Return TryCast(Nodes(item), SettingsNode).GetNode(String.Join("~", items.ToArray()))
				Else
					Return TryCast(Nodes(item), SettingsNode)
				End If
			Else
				Throw New InvalidCastException()
			End If
		Else
			Throw New KeyNotFoundException()
		End If
	End Function

	''' <summary>
	''' Return the value of a path. (Usefull?)
	''' </summary>
	''' <typeparam name="T">Type of returned value</typeparam>
	''' <param name="path">Path to the value</param>
	''' <returns>Value of the path as T</returns>
	''' <exception cref="System.ArgumentException">Thrown if path is empty.</exception>
	''' <exception cref="System.Collections.Generic.KeyNotFoundException">Thrown if current nodes do not contain part of path</exception>
	''' <exception cref="System.InvalidCastException">Thrown if part of path is not of type T</exception>
	Public Function GetNode(Of T)(path As String) As T
		If String.IsNullOrEmpty(path) Then
			Throw New ArgumentNullException()
		End If

		Dim items As New Queue(Of String)(path.Split("~"c))
		Dim item As String = items.Dequeue()

		If Nodes.ContainsKey(item) Then
			If items.Count() > 0 Then
				If Nodes(item).[GetType]() Is GetType(SettingsNode) Then
					Return DirectCast(TryCast(Nodes(item), SettingsNode).GetNode(Of T)(String.Join("~", items.ToArray())), T)
				Else
					Throw New InvalidCastException()
				End If
			ElseIf Nodes(item).[GetType]() Is GetType(T) Then
				Return DirectCast(Nodes(item), T)
			Else
				Throw New InvalidCastException()
			End If
		Else
			Throw New KeyNotFoundException()
		End If
	End Function
End Class
